#pragma once
#include "utils.h"

class View;

struct GameOptions {
    int x_sz;
    int y_sz;
    unsigned char food_move_percent;
    unsigned char init_len;
    unsigned char foods_count;
    struct {
        uint16_t snake_timer;
        uint16_t food_timer;
        uint8_t  food_move_percent;
    } controller;
};

class Food: public OneDirectionList {
public:
    Food(): OneDirectionList(){ }
    ~Food() { OneDirectionList::~OneDirectionList(); }
};

class Snake: public OneDirectionList {
public:
    void init(Point &point, uint8_t size) {
        Point p = point;
        for (int i=0;i<size;i++) {
            auto item = new ListItem(p);
            //item->data = (void *)Action::GA_MOVE_RIGHT;
            this->add_tail(item);
            p.x--;
        }
    }
    void move_to(Point point){
        this->head->tail->point = point;
        this->head = this->head->tail;
    }
    void expand_to(Point point){
        auto * item = new ListItem(point);
        item->tail = this->head->tail;
        this->head->tail = item;
        this->head = item;
    }
    explicit Snake(): OneDirectionList(){ }
};

enum GameState {
    GS_PAUSE,
    GS_RUN,
    GS_DONE
};

enum MoveAction {
    MV_RIGHT,
    MV_UP,
    MV_LEFT,
    MV_DOWN
};


class Game {
private:
    friend class View;
protected:
    Food * food;
    Snake * snake;

    void new_food() {
        Point f(0, 0);
        do {
            f.rnd(this->options->x_sz, this->options->y_sz);
        } while (this->snake->find(f) || (this->food != nullptr && this->food->find(f)));
        this->food->add_tail(new ListItem(f));
    }

public:
    GameOptions * options;
    GameState state = GameState::GS_PAUSE;
    MoveAction move = MoveAction::MV_RIGHT;

    bool in_field(Point &point) {
        return (point.x>=0) && (point.y>=0) && (point.x<this->options->x_sz) && (point.y < this->options->y_sz);
    }

    bool can_move_to(Point &new_point) {
        if (!this->in_field(new_point))
            return false;
        ListItem * item = this->snake->find(new_point);
        return ((item == nullptr) || (item == this->snake->getHead()->tail));
    }

    explicit Game(GameOptions &options) {
        this->options = &options;
        this->food = new Food();
        this->snake = new Snake();
    }

    ~Game() {
        delete this->food;
        delete this->snake;
    }

    void move_foods() {
        ListItem * head = this->food->getHead();
        if (head == nullptr)
            return;
        ListItem * current = head;
        do {
            if ((random() % 100)<this->options->food_move_percent) {
                auto new_point = current->point;
                new_point.random_inc();
                if (in_field(new_point) && !this->food->find(new_point) && !this->snake->find(new_point))
                    current->point = new_point;
            }
            current = current->tail;
        } while(head!=current);

    }

    void update_food(ListItem * item) {
        Point f(item->point.x, item->point.y);
        do {
            f.rnd(this->options->x_sz, this->options->y_sz);
        } while (this->snake->find(f) || this->food->find(f));
        item->point = f;
    }

    void move_snake() {
        if (this->state==GS_RUN) {
            Point new_point = this->snake->getHead()->point;
            switch (this->move) {
                case MoveAction::MV_UP:
                    new_point.y--;
                    break;
                case MoveAction::MV_DOWN:
                    new_point.y++;
                    break;
                case MoveAction::MV_LEFT:
                    new_point.x--;
                    break;
                case MoveAction::MV_RIGHT:
                    new_point.x++;
                    break;
                default:
                    break;
            }
            if (can_move_to(new_point)) {
                ListItem * f = this->food->find(new_point);
                if (f!= nullptr) {
                    this->snake->expand_to(new_point);
                    this->update_food(f);
                } else
                    this->snake->move_to(new_point);
                this->snake->getHead()->data = (void *)(this->move);
            } else {
                this->state = GS_DONE;
            }
        }
    }

    void reset() {
        this->food->release();
        this->snake->release();
        this->state = GameState::GS_PAUSE;
        this->move = MoveAction::MV_RIGHT;
        Point center(this->options->x_sz/2, this->options->y_sz/2);
        this->snake->init(center, this->options->init_len);
        for (uint8_t i=0;i<this->options->foods_count;i++)
            new_food();
        //this->action = Action::GA_PAUSE;
    }

};